import os
from click.testing import CliRunner
import xmltodict
import unittest
from unittest import mock
from collections import namedtuple
from pathlib import Path
import shutil
import constant


class CliTestBase(unittest.TestCase):
    def setUp(self) -> None:
        self._to_clean_mock_patchers = []
        self.base_path = Path(__file__).parents[0]
        self._mock_data_path = self.base_path.joinpath("mock_data")
        os.environ["CI-SETTINGS"] = str(self._mock_data_path.joinpath("config.yaml"))
        self.runner = CliRunner()

    def _requests_get(self, **kwargs):
        raise NotImplementedError

    def _requests_post(self, **kwargs):
        raise NotImplementedError

    def _requests_delete(self, **kwargs):
        raise NotImplementedError

    def _requests_put(self, **kwargs):
        raise NotImplementedError

    def _config(self, **kwargs):
        raise NotImplementedError

    def mock_requests_get(self, **kwargs):
        """mock_requests_get"""
        self._to_update_kw_and_make_mock(
            "from conf import config",
            effect=self._config,
            **kwargs,
        )

    def requests_get(self, **kwargs):
        self._to_update_kw_and_make_mock(
            "request.get", effect=self._requests_get, **kwargs
        )

    def requests_post(self, **kwargs):
        self._to_update_kw_and_make_mock(
            "request.post", effect=self._requests_post, **kwargs
        )

    def requests_put(self, **kwargs):
        self._to_update_kw_and_make_mock(
            "request.put", effect=self._requests_put, **kwargs
        )

    def requests_delete(self, **kwargs):
        self._to_update_kw_and_make_mock(
            "request.get", effect=self._requests_delete, **kwargs
        )

    def _to_add_cleanup(self):
        """_to_add_cleanup"""
        for tc in self._to_clean_mock_patchers:
            self.addCleanup(tc.stop)
        self._to_clean_mock_patchers.clear()

    def _create_patch(self, mock_name, **kwargs):
        """create_patch

        Args:
            method_name (str): mock method or attribute name

        """
        patcher = mock.patch(mock_name, **kwargs)
        self._to_clean_mock_patchers.append(patcher)
        patcher.start()

    def _to_update_kw_and_make_mock(self, mock_name, effect=None, **kwargs):
        """_to_update_kw_and_make_mock

        Args:
            mock_name (str):  mock method or attribute name
            effect (Any, optional): side effect value

        Raises:
            ValueError: If the side_effect or return_value keyword parameter is not specified
                        specify the value of the effect keyword parameter
        """
        if "side_effect" not in kwargs and "return_value" not in kwargs:
            if effect is None:
                raise ValueError(
                    "If the side_effect or return_value keyword parameter is not specified,"
                    "specify the value of the effect keyword parameter"
                )
            kwargs["side_effect"] = effect
        self._create_patch(mock_name, **kwargs)

    def dict_to_obj(self, args):
        """
        字典转对象
        Args:
            args (_type_): _description_
        """
        if isinstance(args, dict):
            raise ValueError("The data is not of type json")
        object_name = namedtuple("object_name", args.keys())(*args.values())
        return object_name

    def command_execution(self, choice, params):

        if not isinstance(params, list):
            raise ValueError("the params error")
        result = self.runner.invoke(choice, params)
        return result.exit_code, result.exception, result.output

    def mock_data_path(self, folder_name, file_name):
        return str(self._mock_data_path.joinpath(folder_name, file_name))

    def xml_to_dict(self, xml_path):
        """
        xml  转化 dict
        Args:
            xml_path (_type_): _description_

        Returns:
            _type_: _description_
        """
        with open(xml_path) as file:
            _dict = xmltodict.parse(file.read())
            return _dict

    def tearDown(self) -> None:
        if Path(constant.RECORDS_COURSE).is_dir():
            shutil.rmtree(constant.RECORDS_COURSE)

        return super().tearDown()
